package com.tanqidi.arcgisandroid.ui

import android.graphics.Color
import android.graphics.Point
import android.os.Environment
import android.text.TextUtils
import android.view.LayoutInflater
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.esri.arcgisruntime.arcgisservices.LabelDefinition
import com.esri.arcgisruntime.arcgisservices.LabelingPlacement
import com.esri.arcgisruntime.concurrent.Job
import com.esri.arcgisruntime.concurrent.ListenableFuture
import com.esri.arcgisruntime.data.*
import com.esri.arcgisruntime.geometry.Geometry
import com.esri.arcgisruntime.geometry.GeometryType
import com.esri.arcgisruntime.geometry.SpatialReference
import com.esri.arcgisruntime.layers.*
import com.esri.arcgisruntime.loadable.LoadStatus
import com.esri.arcgisruntime.mapping.labeling.SimpleLabelExpression
import com.esri.arcgisruntime.mapping.view.Graphic
import com.esri.arcgisruntime.mapping.view.GraphicsOverlay
import com.esri.arcgisruntime.mapping.view.MapView
import com.esri.arcgisruntime.ogc.kml.KmlDataset
import com.esri.arcgisruntime.symbology.SimpleLineSymbol
import com.esri.arcgisruntime.symbology.TextSymbol
import com.esri.arcgisruntime.tasks.geodatabase.*
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.gson.Gson
import com.tanqidi.arcgisandroid.App
import com.tanqidi.arcgisandroid.R
import com.tanqidi.arcgisandroid.base.diyJetpack.BusMutableLiveData
import com.tanqidi.arcgisandroid.base.utils.*
import com.tanqidi.arcgisandroid.data.layerConfig.LayerConfig.Companion.TYPE_POINT
import com.tanqidi.arcgisandroid.data.layerConfig.LayerConfig.Companion.TYPE_POLYGON
import com.tanqidi.arcgisandroid.data.layerConfig.LayerConfig.Companion.TYPE_POLYLINE
import com.tanqidi.arcgisandroid.data.project.Project
import com.tanqidi.arcgisandroid.data.project.ProjectRepository
import com.tanqidi.arcgisandroid.databinding.DialogLayoutBinding
import com.tanqidi.arcgisandroid.databinding.ItemProgressBarBinding
import dagger.hilt.android.lifecycle.HiltViewModel
import io.reactivex.rxjava3.core.Single
import kotlinx.coroutines.*
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
import javax.inject.Inject

@HiltViewModel
class HomeViewModel @Inject constructor(
    private val projectRepository: ProjectRepository
) : ViewModel() {

    private val MAP_TPK_INDEX = 0
    private val MAP_KML_INDEX = 1
    private val MAP_SHP_INDEX = 2
    private val MAP_GEODATABASE_INDEX = 3
    private val MAP_GEOJSON_INDEX = 4
    //这个index用来加载项目数据，geojson或者shp也或者featureServer，存储方式只用其中的一种，不能三种同时加载进来
    private val MAP_PROJECT_FEATURE_INDEX = 5

    //存储项目数据的FeatureTable
    val featureTable = mutableMapOf<String, FeatureTable>()

    lateinit var mapView: MapView

    //查询要素livedata
    val queryFeatureLiveData = BusMutableLiveData<Feature>()
    //本地kml，shp，tpk等资源加载状态livedata
    val resourceLoadStatusLiveData = BusMutableLiveData<Boolean>()
    //在线或者离线 切换的livedata
    val onlineOrOfflineSwitchLiveData = BusMutableLiveData<Boolean>()

    fun getProjectByName(name: String) : Project {
        return projectRepository.getProjectByName(name)
    }

    suspend fun loadTpkToMapView() = withContext(Dispatchers.IO){
        val groupLayer = GroupLayer()
        getMapFromSPUtilsByResourceId(R.string.tpkMapJson)?.forEach {
            val fileName = it.key.toString()
            val filePath = it.value.toString()

            val tileCache = TileCache(filePath)
            val arcGISTiledLayer = ArcGISTiledLayer(tileCache)
            //把name也设置给layer，这样后续图层定位可以用到
            arcGISTiledLayer.name = fileName
            groupLayer.layers.add(arcGISTiledLayer)
        }
        addGroupLayerToMapViewOperationalLayers(groupLayer,MAP_TPK_INDEX)
    }

    suspend fun loadKmlToMapView() = withContext(Dispatchers.IO){
        val groupLayer = GroupLayer()
        getMapFromSPUtilsByResourceId(R.string.kmlMapJson)?.forEach {
            val fileName = it.key.toString()
            val filePath = it.value.toString()

            val kmlDataset = KmlDataset(filePath)
            val kmlLayer = KmlLayer(kmlDataset)
            //把name也设置给layer，这样后续图层定位可以用到
            kmlLayer.name = fileName
            groupLayer.layers.add(kmlLayer)
        }
        addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_KML_INDEX)
    }

    suspend fun loadShpToMapView() = withContext(Dispatchers.IO) {
        val groupLayer = GroupLayer()
        //是空的，groupLayer里面什么都没有只是存放进里面占个位置
        addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_SHP_INDEX)

        val asyncJobList = mutableListOf<Single<Any>>()
        getMapFromSPUtilsByResourceId(R.string.shpMapJson)?.forEach {
            val fileName = it.key.toString()
            //这里得到的filePath其实是shp的父目录，需要得到父目录下面的xxx.shp加载进来就行了
            val filePath = it.value.toString()

            File(filePath).listFiles()?.filter { it.name.contains(".shp") }?.forEach {
                val fileName = it.name
                val absolutePath = it.absolutePath

                //异步加载，添加进来
                val asyncJob = Single.create<Any> { emitter ->
                    val shapefileFeatureTable = ShapefileFeatureTable(absolutePath)
                    shapefileFeatureTable.addDoneLoadingListener {
                        val featureLayer = FeatureLayer(shapefileFeatureTable)
                        groupLayer.layers.add(featureLayer)
                        //把name也设置给layer，这样后续图层定位可以用到
                        featureLayer.name = fileName

                        emitter.onSuccess(true)
                    }
                    shapefileFeatureTable.loadAsync()
                }
                asyncJobList.add(asyncJob)
            }
        }
        //全部异步任务完成后再添加进来
        Single.zip(asyncJobList) { objects -> objects }.subscribe({
            addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_SHP_INDEX)
        }, {
            addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_SHP_INDEX)
        })
    }

    private fun getMapFromSPUtilsByResourceId(sourceId:Int) : Map<*, *>? {
        val mapJson = SPUtils.getInstance().getString(App.app!!.getString(sourceId))
        return Gson().fromJson(mapJson, Map::class.java)
    }

    private fun addGroupLayerToMapViewOperationalLayers(groupLayer: GroupLayer, index:Int) {
        mapView.map?.operationalLayers?.apply {
            //上来直接移除，不管有没有
            try {
                removeAt(index)
            } catch (e: Exception) {
                //e.printStackTrace()
            }
            add(index, groupLayer)
        }
    }

    suspend fun loadGeodatabaseToMapView() = withContext(Dispatchers.IO){
        val groupLayer = GroupLayer()
        //是空的，groupLayer里面什么都没有只是存放进里面占个位置
        addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_GEODATABASE_INDEX)

        val asyncJobList = mutableListOf<Single<Any>>()
        getMapFromSPUtilsByResourceId(R.string.gdbMapJson)?.forEach {
            val fileName = it.key.toString()
            val filePath = it.value.toString()
            val geodatabase = Geodatabase(filePath)
            geodatabase.addDoneLoadingListener {
                val tables = geodatabase.geodatabaseFeatureTables
                tables.filter { !it.displayName.endsWith("ATTACH") }.forEachIndexed { index, geodatabaseFeatureTable ->
                    val job = Single.create<Any> { emit ->
                        geodatabaseFeatureTable.addDoneLoadingListener {
                            var tableName = geodatabaseFeatureTable.tableName
                            tableName = if(tableName.contains("_")){
                                tableName.split("_")[1]
                            } else {
                                tableName
                            }
                            val featureLayer = FeatureLayer(geodatabaseFeatureTable)
                            featureLayer.name = tableName
                            groupLayer.layers.add(featureLayer)

                            emit.onSuccess(true)
                        }
                        geodatabaseFeatureTable.loadAsync()
                    }
                    asyncJobList.add(job)
                }
                //全部异步任务完成后再添加进来
                Single.zip(asyncJobList) { objects -> objects }.subscribe({
                    addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_GEODATABASE_INDEX)
                }, {
                    it.printStackTrace()
                    it.message?.let { it1 -> showToast(it1) }
                })
            }
            geodatabase.loadAsync()
        }
    }

    suspend fun loadGeoJsonToMapView() = withContext(Dispatchers.IO) {
        val groupLayer = GroupLayer()
        getMapFromSPUtilsByResourceId(R.string.geojsonMapJson)?.forEach {
            val fileName = it.key.toString()
            val filePath = it.value.toString()
            val jsonContent = FileUtil.readFile(File(filePath))
            val featureCollection = FeatureCollection.fromJson(jsonContent)
            val featureCollectionLayer = FeatureCollectionLayer(featureCollection)
            featureCollectionLayer.name = fileName
            groupLayer.layers.add(featureCollectionLayer)
        }
        addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_GEOJSON_INDEX)
    }

    /**
     * 清除选中效果
     */
    private fun clearSelection() {
        featureTable.values.forEachIndexed { index, featureTable ->
            featureTable.featureLayer?.clearSelection()
        }
    }
    /**
     * 根据屏幕中的screenPoint点，来查询feature要素，最后的数字10是最大返回10个要素的意思，
     * 一般写几个就行了，不然缩放到省级别点击地图密集要素不就把所有要素都给你查询出来了
     * mapView.identify有好几个方法的，你还可以更精细化的选择方法来实现你点击查询要素的功能
     * 20.0 是点击的范围，点击命中要素的几率就会越大
     */
    fun queryFeatureByScreenCenterPoint(screenPoint: Point) {
        //因为下面查询做了选中效果，这里先清空一下选中效果
        clearSelection()

        val identifyLayersAsync = mapView.identifyLayersAsync(screenPoint, 20.0, false, 1)
        identifyLayersAsync.addDoneListener {
            identifyLayersAsync.get().forEachIndexed { index, identifyLayerResult ->
                //这个结果是geojson的要素内容
                identifyLayerResult.sublayerResults.forEachIndexed { index, identifyLayerResult ->
                    identifyLayerResult.elements.forEachIndexed { index, geoElement ->
                        if(geoElement is Feature){
                            geoElement.featureTable.featureLayer.selectFeature(geoElement)
                            //所有feature的attribute都是懒加载的，如果不来一次空遍历attributes默认里面的value都是空的，初次看到这个效果开始自我怀疑我存储的内容哪去了？？？
                            geoElement.attributes.forEach {
                                val key = it.key
                                val value = it.value
                            }
                            queryFeatureLiveData.postValue(geoElement)
                        }
                    }
                }

                //这个结果是shp和featureServer还有geodatabase的要素内容
                identifyLayerResult.elements.forEachIndexed { index, geoElement ->
                    if(geoElement is Feature){
                        //选中要素，高亮显示出来
                        geoElement.featureTable?.featureLayer?.selectFeature(geoElement)
                        //所有feature的attribute都是懒加载的，如果不来一次空遍历attributes默认里面的value都是空的，初次看到这个效果开始自我怀疑我存储的内容哪去了？？？
                        geoElement.attributes.forEach {
                            val key = it.key
                            val value = it.value
                        }
                        queryFeatureLiveData.postValue(geoElement)
                    }
                }

            }
        }
    }

    /**
     * 添加要素，在里面会判断当前是属于什么存储方式
     */
    fun addFeature(geometry: Geometry, layerName: String, dataCollectMap: MutableMap<String, Any?>) {
        //存储类型
        when(getSPUtils().getString(getString(R.string.storage_type), getString(R.string.storage_geojson))){
            getString(R.string.storage_geojson) -> {
                val featureCollectionTable = featureTable[layerName] as FeatureCollectionTable
                val createFeature = featureCollectionTable.createFeature(dataCollectMap, geometry)
                val addFeatureAsync = featureCollectionTable.addFeatureAsync(createFeature)
                applyEdit(addFeatureAsync, object : CallBack {
                    override fun callBack(index: Int) {
                        GlobalScope.launch {
                            //保存到本地
                            kotlin.runCatching {
                                featureCollectionSaveToJson(featureCollection)
                            }.onFailure {
                                it.printStackTrace()
                                it.message?.let { it1 -> showToast(it1) }
                            }
                        }
                    }
                })
            }
            getString(R.string.storage_shp) -> {
                val shapefileFeatureTable = featureTable[layerName] as ShapefileFeatureTable
                val createFeature = shapefileFeatureTable.createFeature(dataCollectMap, geometry)
                val addFeatureAsync = shapefileFeatureTable.addFeatureAsync(createFeature)
                applyEdit(addFeatureAsync)
            }
            getString(R.string.storage_geodatabase), getString(R.string.storage_feature_server_offline), -> {
                val geodatabaseFeatureTable = featureTable[layerName] as GeodatabaseFeatureTable
                val createFeature = geodatabaseFeatureTable.createFeature(dataCollectMap, geometry)
                /**
                 * 添加附件，目前只支持geodatabase，因为这个比较简单其他的需要自己手写存储方式了，用feature的 globalId做映射，一对多关系
                 */
                val localMediaList = HomeFragment.localMediaList
                if(localMediaList.isNotEmpty()){
                    (createFeature as ArcGISFeature).let {
                        localMediaList.forEachIndexed { index, localMedia ->
                            val realPath = localMedia.realPath
                            val readBytes = File(realPath).inputStream().readBytes()

                            val mimeType = localMedia.mimeType
                            val fileName = localMedia.fileName
                            try {
                                val get = it.addAttachmentAsync(readBytes, mimeType, fileName).get()
                            } catch (e: Exception) {
                                e.printStackTrace()
                            }
                        }
                    }
                }
                val addFeatureAsync = geodatabaseFeatureTable.addFeatureAsync(createFeature)
                applyEdit(addFeatureAsync)
            }

            getString(R.string.storage_feature_server) -> {
                val serviceFeatureTable = featureTable[layerName] as ServiceFeatureTable
                val createFeature = serviceFeatureTable.createFeature(dataCollectMap, geometry)
                val localMediaList = HomeFragment.localMediaList
                //同样的，featureServer存储模式，也支持附件操作的，只是代码在保存的地方有点稍微不一样
                if(localMediaList.isNotEmpty()){
                    (createFeature as ArcGISFeature).let {
                        localMediaList.forEachIndexed { index, localMedia ->
                            val realPath = localMedia.realPath
                            val readBytes = File(realPath).inputStream().readBytes()

                            val mimeType = localMedia.mimeType
                            val fileName = localMedia.fileName
                            try {
                                val get = it.addAttachmentAsync(readBytes, mimeType, fileName).get()
                            } catch (e: Exception) {
                                e.printStackTrace()
                            }
                        }
                    }
                }
                //保存要素
                serviceFeatureTable.addFeatureAsync(createFeature).addDoneListener {
                    applyServiceFeatureTableEdit(serviceFeatureTable)
                }
            }
        }
    }

    /**
     * 更新要素
     */
    fun updateFeature(geometry: Geometry, layerName: String, dataCollectMap: MutableMap<String, Any?>, updateFeature: Feature) {
        updateFeature.geometry = geometry
        dataCollectMap.forEach {
            updateFeature.attributes[it.key] = it.value
        }
        //存储类型
        when(getSPUtils().getString(getString(R.string.storage_type), getString(R.string.storage_geojson))){
            getString(R.string.storage_geojson) -> {
                val featureCollectionTable = featureTable[layerName] as FeatureCollectionTable
                val updateFeatureAsync = featureCollectionTable.updateFeatureAsync(updateFeature)
                applyEdit(updateFeatureAsync, object : CallBack {
                    override fun callBack(index: Int) {
                        GlobalScope.launch {
                            featureCollectionSaveToJson(featureCollection)
                        }
                    }
                })
            }
            getString(R.string.storage_shp) -> {
                val shapefileFeatureTable = featureTable[layerName] as ShapefileFeatureTable
                val updateFeatureAsync = shapefileFeatureTable.updateFeatureAsync(updateFeature)
                applyEdit(updateFeatureAsync)
            }
            getString(R.string.storage_geodatabase), getString(R.string.storage_feature_server_offline) -> {
                val geodatabaseFeatureTable = featureTable[layerName] as GeodatabaseFeatureTable
                val updateFeatureAsync = geodatabaseFeatureTable.updateFeatureAsync(updateFeature)
                applyEdit(updateFeatureAsync)
            }
            getString(R.string.storage_feature_server) -> {
                val serviceFeatureTable = featureTable[layerName] as ServiceFeatureTable
                serviceFeatureTable.updateFeatureAsync(updateFeature).addDoneListener {
                    applyServiceFeatureTableEdit(serviceFeatureTable)
                }
            }
        }

        //清除选中效果
        clearSelection()
    }


    /**
     * 根据屏幕中心点删除要素
     */
    fun deleteFeatureByScreenCenterPoint() {
        val center = mapView.visibleArea.extent.center
        val screenPoint = mapView.locationToScreen(center)
        val identifyLayersAsync = mapView.identifyLayersAsync(screenPoint, 10.0, false, 1)
        identifyLayersAsync.addDoneListener {
            identifyLayersAsync.get().forEachIndexed { index, identifyLayerResult ->
                val sublayerResults = identifyLayerResult.sublayerResults
                if(sublayerResults.isNotEmpty()){
                    sublayerResults.get(0).elements.apply {
                        if(isNotEmpty()){
                            val result = get(0)
                            if(result is Feature){
                                showConfirmDialog(mapView.context, "提示", "确定删除要素吗") { dialog, which ->
                                    val featureTable = featureTable[result.featureTable.displayName]!!
                                    val deleteFeatureAsync = featureTable.deleteFeatureAsync(result)
                                    applyEdit(deleteFeatureAsync, object : CallBack {
                                        override fun callBack(index: Int) {
                                            GlobalScope.launch {
                                                featureCollectionSaveToJson(featureCollection)
                                            }
                                        }
                                    })
                                }

                            }
                        }
                    }
                }
                identifyLayerResult.elements.forEachIndexed { index, geoElement ->
                    if(geoElement is Feature){
                        val name = geoElement.featureTable.featureLayer.name
                        showConfirmDialog(mapView.context, "提示", "确定删除要素吗") { dialog, which ->
                            val featureTable = featureTable[name]!!
                            val deleteFeatureAsync = featureTable.deleteFeatureAsync(geoElement)
                            //如果是featureServer还得需要应用编辑一下才能删掉
                            if(featureTable is ServiceFeatureTable){
                                featureTable.applyEditsAsync().addDoneListener {
                                    applyServiceFeatureTableEdit(featureTable)
                                }
                            } else {
                                applyEdit(deleteFeatureAsync)
                            }

                        }
                    }
                }

            }
        }
    }

    /**
     * HomeFragment加载主项目的入口
     */
    suspend fun loadMainProjectToMapView() = withContext(Dispatchers.IO){
        if(TextUtils.isEmpty(getSPUtils().getString(getString(R.string.main_project), null))){
            viewModelScope.launch { showToast("请配置主项目") }
            resourceLoadStatusLiveData.postValue(true)
            return@withContext
        }
        featureTable.clear()

        //获取存储类型，默认是geojson
        when(getSPUtils().getString(getString(R.string.storage_type), getString(R.string.storage_geojson))){
            getString(R.string.storage_geojson) -> {
                loadProjectGeoJsonToMapView()
            }
            /**
             * 以shp和geodatabase存储默认不会在这里创建文件，你需要配置完毕项目和里面的各种图层和字段后
             * 回到项目配置，点击项目右上角的 齿轮 菜单，点击生成shp或者geodatabase文件，然后这里就可以用了
             */
            getString(R.string.storage_shp) -> {
                loadProjectShpToMapView()
            }
            getString(R.string.storage_geodatabase) -> {
                loadProjectGeodatabaseToMapView()
            }
            getString(R.string.storage_feature_server) -> {
                loadProjectFeatureServerToMapView()
            }
            getString(R.string.storage_feature_server_offline) -> {
                loadProjectGeodatabaseToMapView(true)
            }
        }
    }

    /**
     * featureServer 数据操作
     */
    private fun loadProjectFeatureServerToMapView() {
        val mainProjectName = SPUtils.getInstance().getString(App.app!!.getString(R.string.main_project))
        val project = projectRepository.getProjectByName(mainProjectName)

        val groupLayer = GroupLayer()
        val asyncJobList = mutableListOf<Single<Any>>()
        val featureLayerUrlList = project.layers.filter { !TextUtils.isEmpty(it.featureLayerUrl) }
        featureLayerUrlList.forEachIndexed { index, layerConfig ->
            val job = Single.create<Any> { emit->
                val serviceFeatureTable = ServiceFeatureTable(layerConfig.featureLayerUrl)
                serviceFeatureTable.requestConfiguration

                serviceFeatureTable.addLoadStatusChangedListener {
                    when(it.newLoadStatus){
                        LoadStatus.LOADED -> {
                            val displayName = serviceFeatureTable.displayName
                            featureTable[if(displayName.contains("_")){
                                displayName.split("_")[1]
                            } else {
                                displayName
                            }] = serviceFeatureTable

                            val featureLayer = FeatureLayer(serviceFeatureTable)
                            groupLayer.layers.add(featureLayer)
                            //开始符号化
                            SymbolUtils.symbol(layerConfig, featureLayer)
                            featureLayer.name = displayName
                            emit.onSuccess(true)
                        }
                        else -> {}
                    }
                }
                serviceFeatureTable.loadAsync()
            }
            asyncJobList.add(job)
        }
        //全部异步任务完成后再添加进来
        Single.zip(asyncJobList) { objects -> objects }.subscribe({
            addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_PROJECT_FEATURE_INDEX)
            resourceLoadStatusLiveData.postValue(true)
        }, {
            it.printStackTrace()
            it.message?.let { it1 -> showToast(it1) }
        })
    }

    /**
     * geoJson 数据操作
     */
    private lateinit var featureCollection: FeatureCollection
    private suspend fun loadProjectGeoJsonToMapView() = withContext(Dispatchers.IO) {
        val groupLayer = GroupLayer()
        addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_PROJECT_FEATURE_INDEX)
        //创建FeatureCollectionTable和字段关系
        val mainProjectName = SPUtils.getInstance().getString(App.app!!.getString(R.string.main_project))
        if(TextUtils.isEmpty(mainProjectName)){
            return@withContext
        }

        val project = projectRepository.getProjectByName(mainProjectName)
        //检查本地有没有主专业json文件
        val dataFolder = Environment.getExternalStorageDirectory().absolutePath + File.separator + getString(R.string.app_name) + File.separator + getString(R.string.project_data) + File.separator + getString(R.string.geojson)
        val jsonFile = File(dataFolder, "$mainProjectName.json")
        if(jsonFile.exists()){
            val jsonContent = FileUtil.readFile(jsonFile)
            featureCollection = FeatureCollection.fromJson(jsonContent)
        } else {
            featureCollection = FeatureCollection()
            //项目结构，只能初始化一次
            project.layers.forEachIndexed { index, layer ->
                val fieldList = layer.fields.map { Field.createString(it.field, it.alias, 32) }
                val geometryType = when(layer.type){
                    TYPE_POINT -> GeometryType.POINT
                    TYPE_POLYLINE -> GeometryType.POLYLINE
                    TYPE_POLYGON -> GeometryType.POLYGON
                    else -> GeometryType.POINT
                }
                // TODO: 一定要给项目图层字段中，设置 field 字段，空了不行
                if(fieldList.isNotEmpty()){
                    //根据图层配置来创建FeatureCollectionTable
                    val featureCollectionTable = FeatureCollectionTable(fieldList, geometryType, SpatialReference.create(4326))
                    featureCollectionTable.title = layer.name
                    featureCollection.tables.add(featureCollectionTable)
                }
            }
        }
        //设置符号化相关
        val featureCollectionLayer = FeatureCollectionLayer(featureCollection)
        featureCollectionLayer.addDoneLoadingListener {
            val layers = project.layers
            layers.forEachIndexed { index, layer ->
                val layers = featureCollectionLayer.layers
                val filter = layers.filter { it.name == layer.name }
                if(filter.isNotEmpty()){
                    val featureLayer = filter[0]
                    featureLayer.name = layer.name
                    SymbolUtils.symbol(layer, featureLayer)
                }
            }
            //把featureTable存起来，添加要素的时候用到
            featureCollection.tables.forEachIndexed { index, featureCollectionTable ->
                featureTable[featureCollectionTable.title] = featureCollectionTable
            }
            groupLayer.layers.add(featureCollectionLayer)
            addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_PROJECT_FEATURE_INDEX)

            resourceLoadStatusLiveData.postValue(true)
        }
        featureCollectionLayer.loadAsync()
    }

    /**
     * shp 数据操作
     */
    private suspend fun loadProjectShpToMapView() = withContext(Dispatchers.IO){
        val groupLayer = GroupLayer()
        //占位
        addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_PROJECT_FEATURE_INDEX)

        val mainProjectName = SPUtils.getInstance().getString(App.app!!.getString(R.string.main_project))
        if(TextUtils.isEmpty(mainProjectName)){
            return@withContext
        }
        //为符号化做准备，先把素有的图层配置拿出来
        val mainProject = projectRepository.getProjectByName(mainProjectName)
        val asyncJobList = mutableListOf<Single<Any>>()
        val dataFolder = Environment.getExternalStorageDirectory().absolutePath + File.separator + getString(R.string.app_name) + File.separator + getString(R.string.project_data) + File.separator + getString(R.string.shp) + File.separator + mainProjectName
        if(!File(dataFolder).exists()){
            viewModelScope.launch { showToast("主项目未创建shp") }
            return@withContext
        }
        File(dataFolder).listFiles()?.forEachIndexed { index, file ->
            val job = Single.create<Any> { emit ->
                //这里得到的file只是每个图层的父目录，还需要遍历该file的子文件xxxx.shp就可以加载了
                val shpfile = file.listFiles().filter { it.name.endsWith(".shp") }[0]
                val shapefileFeatureTable = ShapefileFeatureTable(shpfile.absolutePath)
                shapefileFeatureTable.addLoadStatusChangedListener {
                    when(it.newLoadStatus){
                        LoadStatus.LOADED -> {
                            //把featureTable存起来，添加要素的时候用到，xxxxx.shp 用逗号分割拿第一个
                            val filename = file.name.split(".")[0]
                            //加载成功，准备添加入mapView
                            val featureLayer = FeatureLayer(shapefileFeatureTable)
                            featureLayer.name = filename
                            groupLayer.layers.add(featureLayer)
                            //符号化
                            val layer = mainProject.layers.filter { it.name == filename }[0]
                            // TODO: shp标注并不是ObjectId 而是 FID
                            if(layer.label == "ObjectId"){
                                layer.label = "FID"
                            }
                            SymbolUtils.symbol(layer, featureLayer)
                            featureTable[filename] = shapefileFeatureTable
                            emit.onSuccess(true)
                        }
                        LoadStatus.FAILED_TO_LOAD,LoadStatus.NOT_LOADED -> {
                            shapefileFeatureTable.retryLoadAsync()
                        }
                        //加载中，应该不用管吧
                        else -> {}
                    }
                }
                shapefileFeatureTable.loadAsync()
            }
            asyncJobList.add(job)
        }
        //全部异步任务完成后再添加进来
        Single.zip(asyncJobList) { objects -> objects }.subscribe({
            addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_PROJECT_FEATURE_INDEX)
            resourceLoadStatusLiveData.postValue(true)
        }, {})
    }

    /**
     * geodatabase 数据操作，重复使用，isFeatureServerOffline如果是true表示从 离线模式读取geodatabase
     */
    private suspend fun loadProjectGeodatabaseToMapView(isFeatureServerOffline: Boolean = false) = withContext(Dispatchers.IO){
        val groupLayer = GroupLayer()
        //占位
        addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_PROJECT_FEATURE_INDEX)

        val mainProjectName = SPUtils.getInstance().getString(App.app!!.getString(R.string.main_project))
        if(TextUtils.isEmpty(mainProjectName)){
            return@withContext
        }
        val databaseDir = if(isFeatureServerOffline){
            //是离线模式，还需要把离线的框给绘制出来
            drawExtent()
            getString(R.string.offline)
        } else {
            getString(R.string.geodatabase)
        }
        val geodatabaseFilePath = Environment.getExternalStorageDirectory().absolutePath + File.separator + getString(R.string.app_name) + File.separator + getString(R.string.project_data) + File.separator + databaseDir + File.separator + mainProjectName+".geodatabase"
        val geodatabaseFile = File(geodatabaseFilePath)
        //如果没有创建geodatabase，不能使用
        if(!geodatabaseFile.exists()){
            viewModelScope.launch { showToast("主项目未创建geodatabase") }
            return@withContext
        }
        //为符号化做准备，先把素有的图层配置拿出来
        val mainProject = projectRepository.getProjectByName(mainProjectName)
        val geodatabase = Geodatabase(geodatabaseFilePath)

        geodatabase.addDoneLoadingListener {
            /**
             * 加载完毕，获取全部的数据表
             * 1、这里为什么需要排除名字包含 ATTACH 的表呢？假设我们有3个表，在创建geodatabase的时候调用了 tableDescription.setHasAttachments(true)
             * 他会自动帮我们创建另外3个表用于存储要素附件，例如 建筑物调查表，就会多出一个建筑物调查表_ATTACH的表，这个附件表对于我们录入要素来说是作用不大的
             * 所以我们需要在这里进行排除一下
             *
             * 2、假设我们录入了超大数量的要素，直接加载或许会出现奇怪的异步问题导致不显示，所以我这里依然使用rxjava异步加载等待
             */
            //异步等待结果
            val asyncJobList = mutableListOf<Single<Any>>()
            val tables = geodatabase.geodatabaseFeatureTables.filter { !it.displayName.endsWith("ATTACH") }
            tables.forEachIndexed { index, geodatabaseFeatureTable ->
                val job = Single.create<Any> { emit ->
                    geodatabaseFeatureTable.addLoadStatusChangedListener {
                        when(it.newLoadStatus){
                            LoadStatus.LOADED -> {
                                /**
                                 * 离线出来的geodatabase的geodatabaseFeatureTable.tableName有的情况会有下划线，前后拼接着一些服务的其他信息，
                                 * 如果包含下划线，就取下标为1的就是正确的名字，如果没下划线就原样执行下去
                                 */
                                var tableName = geodatabaseFeatureTable.tableName
                                tableName = if(tableName.contains("_")){
                                    tableName.split("_")[1]
                                } else {
                                    tableName
                                }
                                val featureLayer = FeatureLayer(geodatabaseFeatureTable)
                                featureLayer.name = tableName
                                //开始符号化
                                val layer = mainProject.layers.filter { it.name == tableName}[0]
                                SymbolUtils.symbol(layer, featureLayer)
                                groupLayer.layers.add(featureLayer)
                                featureTable[tableName] = geodatabaseFeatureTable
                                emit.onSuccess(true)
                            }
                            LoadStatus.NOT_LOADED, LoadStatus.FAILED_TO_LOAD -> {
                                geodatabaseFeatureTable.retryLoadAsync()
                            }
                            else -> { }
                        }
                    }
                    geodatabaseFeatureTable.loadAsync()
                }
                asyncJobList.add(job)
            }
            //全部异步任务完成后再添加进来
            Single.zip(asyncJobList) { objects -> objects }.subscribe({
                addGroupLayerToMapViewOperationalLayers(groupLayer, MAP_PROJECT_FEATURE_INDEX)
                resourceLoadStatusLiveData.postValue(true)
            }, {
                it.printStackTrace()
                it.message?.let { it1 -> showToast(it1) }
            })
        }
        geodatabase.loadAsync()
    }

    /**
     * 将featureCollection的内容保存到本地，是geojson
     */
    private suspend fun featureCollectionSaveToJson(featureCollection: FeatureCollection) = withContext(Dispatchers.IO){
        //根据目录来遍历得到所有的文件
        val mainProjectName = SPUtils.getInstance().getString(getString(R.string.main_project))
        val dataFolder = Environment.getExternalStorageDirectory().absolutePath + File.separator + getString(R.string.app_name) + File.separator + getString(R.string.project_data) + File.separator + getString(R.string.geojson)
        FileUtil.write(File(dataFolder, "$mainProjectName.json"), featureCollection.toJson())
    }

    /**
     * 应用操作，是否成功
     */
    private fun applyEdit(listenableFuture: ListenableFuture<Void>, callBack: CallBack? = null){
        listenableFuture.addDoneListener {
            try {
                listenableFuture.get()
                viewModelScope.launch { showToast("操作成功") }
                callBack?.let { it.callBack( -1 ) }
            } catch (e: Exception) {
                e.printStackTrace()
                viewModelScope.launch { showToast(e.message!!) }
            }
        }
    }
    /**
     * 应用操作，是否成功
     */
    private fun applyServiceFeatureTableEdit(serviceFeatureTable: ServiceFeatureTable){
        val applyEditsAsync = serviceFeatureTable.applyEditsAsync()
        applyEditsAsync.addDoneListener {
            try {
                val get = applyEditsAsync.get()
                viewModelScope.launch { showToast("操作成功") }
            } catch (e: Exception) {
                e.printStackTrace()
                viewModelScope.launch { showToast(e.message!!) }
            }
        }
    }

    /**
     * featureServer离线模式，开始生成geodatabase和各种配置
     */
    val generateGeodatabaseJobList = mutableListOf<GenerateGeodatabaseJob>()
    fun featureServerOnlineToOffline() {
        generateGeodatabaseJobList.clear()
        //创建进度条弹窗
        val createProgressDialog = createProgressDialog("离线中...")
        //默认把确定禁止点击，失败失败才能点击
        createProgressDialog.accept.apply {
            isEnabled = false
            setOnClickListener {
                currentShowDialog?.dismiss()
            }
        }
        //进度条中如果中途想要取消，可以使用这个来进行取消job任务
        createProgressDialog.cancel.setOnClickListener {
            generateGeodatabaseJobList.forEachIndexed { index, generateGeodatabaseJob ->
                generateGeodatabaseJob.cancel()
            }
            //关闭dialog
            currentShowDialog?.dismiss()
        }
        //离线的屏幕范围
        val extent = mapView.visibleArea.extent
        //异步等待结果
        val asyncJobList = mutableListOf<Single<Any>>()
        //从featureTable得到所有的featureServer排重一下
        val featureServerList = featureTable.values.map {
            val uri = (it as ServiceFeatureTable).uri
            val substring = uri.substring(0, uri.lastIndexOf("/"))
            substring
        }.toSet()
        val offlineDatabasePath = Environment.getExternalStorageDirectory().absolutePath + File.separator + getString(R.string.app_name) + File.separator + getString(R.string.project_data) + File.separator + getString(R.string.offline)
        featureServerList.forEachIndexed { index, featureServerUrl ->
            val job = Single.create<Any> { emit ->
                val geodatabaseSyncTask = GeodatabaseSyncTask(featureServerUrl)
                geodatabaseSyncTask.addDoneLoadingListener {
                    // create generate geodatabase parameters for the current extent
                    var progressView: ItemProgressBarBinding?
                    val defaultParameters = geodatabaseSyncTask.createDefaultGenerateGeodatabaseParametersAsync(extent)
                    defaultParameters.addDoneListener {
                        progressView = ItemProgressBarBinding.inflate(LayoutInflater.from(mapView.context), createProgressDialog.progressBarContainer, true)
                        try {
                            val parameters = defaultParameters.get()
                            /**
                             * 假设一个FeatureServer里面有多达100个FeatureLayer，采用默认的离线规则不合适，离线完成的时候它会生成一个Geodatabase里面会有100个GeodatabaseFeatureTable表
                             * 这会很大程度影像我们的离线速度，在这种情况我们就需要按需离线，例如我需要离线FeatureLayer的Id为8,12,23,50的4个表，默认parameters.layerOptions里面有100个id，先
                             * 清空然后再放进来这4个id，这个时候它就只会离线生成这4个GeodatabaseFeatureTable，速度和性能这样就上去了
                             */
                            val mainProjectName = getSPUtils().getString(getString(R.string.main_project))
                            val mainProject = projectRepository.getProjectByName(mainProjectName)
                            val featureLayerIdList = mainProject.layers.map { it.id }
                            parameters.layerOptions.apply {
                                clear()
                                featureLayerIdList.forEachIndexed { index, id ->
                                    add(GenerateLayerOption(id.toLong()).apply {
                                        //whereClause = "如果在离线的时候，可以过滤指定的离线要素"
                                    })
                                }
                            }
                            //开启附件，离线将会支持附件操作，这里表示离线下载的要素也会把附件下载下来，同时也支持离线编辑要素
                            parameters.isReturnAttachments = true
                            //将当前的url找到对应的layer作为名字
                            val localGeodatabasePath = "$offlineDatabasePath/${mainProjectName}.geodatabase"
                            // create and start the job
                            val generateGeodatabaseJob = geodatabaseSyncTask.generateGeodatabase(parameters, localGeodatabasePath)
                            generateGeodatabaseJobList.add(generateGeodatabaseJob)
                            //查看进度
                            progressView!!.title.text = mainProjectName
                            generateGeodatabaseJob.addProgressChangedListener {
                                createProgressDialog.cancel.isEnabled = true

                                progressView!!.progressBar.progress = generateGeodatabaseJob.progress
                                progressView!!.progressTextView.text = "${generateGeodatabaseJob.progress}%"
                            }
                            // get geodatabase when done
                            generateGeodatabaseJob.addJobDoneListener {
                                if (generateGeodatabaseJob.status == Job.Status.SUCCEEDED) {
                                    emit.onSuccess(index)
                                } else {
                                    progressView!!.error.text = "离线失败，请重试"
                                    emit.onError(RuntimeException("离线失败，请重试"))
                                }
                            }
                            //开始！
                            generateGeodatabaseJob.start()
                        } catch (e: Exception) {
                            e.printStackTrace()
                            emit.onError(RuntimeException("离线失败，请重试"))
                            progressView?.error?.text = "离线失败，请重试"
                        }
                    }
                }
                geodatabaseSyncTask.loadAsync()
            }
            asyncJobList.add(job)
        }
        //等待
        Single.zip(asyncJobList) { objects -> objects }.subscribe({
            //将范围写入json
            getSPUtils().apply {
                put(getString(R.string.extent), extent.toJson())
                //将存储模式设置成为storage_feature_server_offline，这个只能通过离线成功来设置，不能通过侧边栏变更存储模式来设置
                put(getString(R.string.storage_type), getString(R.string.storage_feature_server_offline))
            }
            //关闭dialog
            currentShowDialog?.dismiss()
            //数据库生成完毕
            onlineOrOfflineSwitchLiveData.postValue(true)
            MaterialAlertDialogBuilder(mapView.context)
                .setTitle("提示")
                .setCancelable(false)
                .setMessage("存储模式切换成功")
                .setPositiveButton("立即重启") { a,b ->
                    com.blankj.utilcode.util.AppUtils.relaunchApp(true)
                }
                .show()

        }, {
            createProgressDialog.accept.isEnabled = true
            //如果有3个数据库，成功了2个，失败了1个，依然全部删除
            File(offlineDatabasePath).listFiles()?.forEachIndexed { index, file ->
                FileUtil.deleteFile(file)
            }
            //失败了
            onlineOrOfflineSwitchLiveData.postValue(false)
        })
    }
    /**
     * 同步
     */
    val syncGeodatabaseJobList = mutableListOf<SyncGeodatabaseJob>()
    fun featureServerOfflineToOnline(){
        syncGeodatabaseJobList.clear()
        //创建进度条弹窗
        val createProgressDialog = createProgressDialog("同步中...")
        //默认把确定禁止点击，失败失败才能点击
        createProgressDialog.accept.apply {
            isEnabled = false
            setOnClickListener {
                currentShowDialog?.dismiss()
            }
        }
        createProgressDialog.cancel.setOnClickListener {
            syncGeodatabaseJobList.forEachIndexed { index, syncGeodatabaseJob ->
                syncGeodatabaseJob.cancel()
            }
            currentShowDialog?.dismiss()
            onlineOrOfflineSwitchLiveData.postValue(true)
        }

        // create parameters for the sync task
        val syncGeodatabaseParameters = SyncGeodatabaseParameters()
        syncGeodatabaseParameters.syncDirection = SyncGeodatabaseParameters.SyncDirection.BIDIRECTIONAL
        syncGeodatabaseParameters.isRollbackOnFailure = false

        val offlineDatabasePath = Environment.getExternalStorageDirectory().absolutePath + File.separator + getString(R.string.app_name) + File.separator + getString(R.string.project_data) + File.separator + getString(R.string.offline)
        //同步异步
        val asyncJobList = mutableListOf<Single<Any>>()
        File(offlineDatabasePath).listFiles()?.forEachIndexed { index, file ->
            val job = Single.create<Any> { emitter ->
                val geodatabase = Geodatabase(file.absolutePath)
                geodatabase.addDoneLoadingListener {
                    if (geodatabase.loadStatus == LoadStatus.LOADED) {
                        // get the layer ID for each feature table in the geodatabase, then add to the sync job
                        geodatabase.geodatabaseFeatureTables.forEach { geodatabaseFeatureTable ->
                            val serviceLayerId = geodatabaseFeatureTable.serviceLayerId
                            val syncLayerOption = SyncLayerOption(serviceLayerId)
                            syncGeodatabaseParameters.layerOptions.add(syncLayerOption)
                        }
                        val geodatabaseSyncTask = GeodatabaseSyncTask(geodatabase.serviceUrl)
                        val syncGeodatabaseJob = geodatabaseSyncTask.syncGeodatabase(syncGeodatabaseParameters, geodatabase)
                        syncGeodatabaseJobList.add(syncGeodatabaseJob)
                        syncGeodatabaseJob.start()
                        //查看进度
                        val progressView = ItemProgressBarBinding.inflate(LayoutInflater.from(mapView.context), createProgressDialog.progressBarContainer, true)
                        progressView.title.text = file.name.split(".")[0]
                        syncGeodatabaseJob.addProgressChangedListener {
                            createProgressDialog.cancel.isEnabled = true

                            progressView.progressBar.progress = syncGeodatabaseJob.progress
                            progressView.progressTextView.text = "${syncGeodatabaseJob.progress}%"
                        }
                        syncGeodatabaseJob.addJobDoneListener {
                            if (syncGeodatabaseJob.status == Job.Status.SUCCEEDED) {
                                emitter.onSuccess(true)
                            } else {
                                progressView.error.text = "同步失败，请重试"
                                emitter.onError(RuntimeException("同步失败，请重试"))

                            }
                        }
                    }
                }
                geodatabase.loadAsync()
            }
            asyncJobList.add(job)
        }

        //等待
        Single.zip(asyncJobList) { objects -> objects }.subscribe({
            getSPUtils().apply {
                //移除离线标识，只需要把存储模式设置成为  featureServer存储模式 即可
                put(getString(R.string.storage_type), getString(R.string.storage_feature_server))
                //去掉范围框json
                remove(getString(R.string.extent))
            }
            //在mapView中去掉那个红色离线框
            mapView.graphicsOverlays.clear()

            //将数据库文件备份到本地
            val backupsFolder = Environment.getExternalStorageDirectory().absolutePath + File.separator + getString(R.string.app_name) + File.separator + getString(R.string.project_data) + File.separator + getString(R.string.backups)
            val format = SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒")
             File(offlineDatabasePath).listFiles()?.forEachIndexed { index, file ->
                FileUtil.copyFile(file, File(backupsFolder, format.format(Date())+".geodatabase").absolutePath)
                file.delete()
            }
            //关闭dialog
            currentShowDialog?.dismiss()
            //同步成功
            onlineOrOfflineSwitchLiveData.postValue(true)

            MaterialAlertDialogBuilder(mapView.context)
                .setTitle("提示")
                .setCancelable(false)
                .setMessage("存储模式切换成功")
                .setPositiveButton("立即重启") { a,b ->
                    com.blankj.utilcode.util.AppUtils.relaunchApp(true)
                }
                .show()
        }, {
            createProgressDialog.accept.isEnabled = true
            onlineOrOfflineSwitchLiveData.postValue(true)
        })
    }

    /**
     * 离线和同步需要的进度条对话框
     */
    private var currentShowDialog: AlertDialog? = null
    private fun createProgressDialog(title: String): DialogLayoutBinding {
        val inflate = DialogLayoutBinding.inflate(LayoutInflater.from(mapView.context))
        val dialog = MaterialAlertDialogBuilder(mapView.context)
            .setTitle(title)
            .setCancelable(false)
            .setView(inflate.root)
        currentShowDialog = dialog.show()
        return inflate
    }

    /**
     * 生成一个离线框
     */
    private fun drawExtent() {
        val json = getSPUtils().getString(getString(R.string.extent))
        val extent = Geometry.fromJson(json)
        val boundarySymbol = SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.RED, 2F)

        val textSymbol = TextSymbol().apply {
            size = 11f
            color = Color.BLUE
            outlineColor = Color.WHITE
            haloColor = Color.WHITE
            haloWidth = 2f
        }
        val labelDefinition = LabelDefinition(SimpleLabelExpression("[label]"), textSymbol).apply {
            isUseCodedValues = true
            placement = LabelingPlacement.AUTOMATIC
        }
        val graphicsOverlay = GraphicsOverlay()
        val attribute = mutableMapOf<String, Any>()
        attribute["label"] = "离线框"
        val boundary = Graphic(extent, attribute, boundarySymbol)
        graphicsOverlay.graphics.add(boundary)
        graphicsOverlay.labelDefinitions.add(labelDefinition)
        //不显示了
        graphicsOverlay.isLabelsEnabled = false
        mapView.graphicsOverlays.apply {
            clear()
            add(graphicsOverlay)
        }
    }
}